package nl.utwente.fmt.vertalerbouw.tarrh;

import java.io.IOException;
import java.io.Reader;

import nl.utwente.fmt.vertalerbouw.tarrh.checker.CheckerException;
import nl.utwente.fmt.vertalerbouw.tarrh.checker.CheckerTreeWalker;
import nl.utwente.fmt.vertalerbouw.tarrh.encoder.Bytecode;
import nl.utwente.fmt.vertalerbouw.tarrh.encoder.EncoderException;
import nl.utwente.fmt.vertalerbouw.tarrh.encoder.EncoderTreeWalker;
import nl.utwente.fmt.vertalerbouw.tarrh.parser.TarrhLexer;
import nl.utwente.fmt.vertalerbouw.tarrh.parser.TarrhParser;
import nl.utwente.fmt.vertalerbouw.tarrh.treewalker.TarrhTreeWalker;
import nl.utwente.fmt.vertalerbouw.tarrh.treewalker.TreeWalker;

import org.antlr.runtime.ANTLRReaderStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.TokenStream;
import org.antlr.runtime.tree.CommonTree;
import org.antlr.runtime.tree.CommonTreeNodeStream;
import org.antlr.runtime.tree.TreeNodeStream;

/**
 * This class contains methods for parsing, checking and encoding Tarrh source
 * code.
 */
public class TarrhUtils {
	/**
	 * Check an abstract syntax tree for semantic errors.
	 * 
	 * @param tree
	 *            the abstract syntax tree to check for semantic errors.
	 * @throws RecognitionException
	 *             if the abstract syntax tree cannot be parsed correctly.
	 * @throws CheckerException
	 *             if an error occurs in the checker.
	 */
	public static void check(CommonTree tree) throws RecognitionException, CheckerException {
		visit(tree, new CheckerTreeWalker());
	}

	/**
	 * Encode an abstract syntax tree as java bytecode.
	 * 
	 * @param className
	 *            the classname of the source code.
	 * @param tree
	 *            the abstract syntax tree to encode.
	 * @return the bytecode instance representing the generated bytecode.
	 * @throws RecognitionException
	 *             if the abstract syntax tree cannot be parsed correctly.
	 * @throws EncoderException
	 *             if an error occurs in the encoder.
	 */
	public static Bytecode encode(String className, CommonTree tree) throws RecognitionException, EncoderException {
		EncoderTreeWalker encoderVisitor = new EncoderTreeWalker(className);
		visit(tree, encoderVisitor);
		return encoderVisitor.toBytecode();
	}

	/**
	 * Encode Tarrh source code as java bytecode.
	 * 
	 * @param className
	 *            the classname of the source code.
	 * @param reader
	 *            the reader from which the source code can be read.
	 * @return
	 * @throws IOException
	 *             if the specified reader cannot be read.
	 * @throws RecognitionException
	 *             if the source code cannot be parsed correctly.
	 * @throws CheckerException
	 *             if an error occurs in the checker.
	 * @throws EncoderException
	 *             if an error occurs in the encoder.
	 */
	public static Bytecode encode(String className, Reader reader) throws IOException, RecognitionException,
			CheckerException, EncoderException {
		CommonTree tree = parse(reader);
		check(tree);
		return encode(className, tree);
	}

	/**
	 * Parse Tarrh source code and produces an abstract syntax tree.
	 * 
	 * @param reader
	 *            the reader from which the source code can be read.
	 * @return the abstract syntax tree corresponding to the specified source
	 *         code.
	 * @throws IOException
	 *             if the specified reader cannot be read.
	 * @throws RecognitionException
	 *             if the source code cannot be parsed correctly.
	 */
	public static CommonTree parse(Reader reader) throws IOException, RecognitionException {
		TarrhLexer lexer = new TarrhLexer(new ANTLRReaderStream(reader));
		TokenStream tokens = new CommonTokenStream(lexer);

		TarrhParser parser = new TarrhParser(tokens);
		TarrhParser.program_return result = parser.program();
		return (CommonTree) result.getTree();
	}

	/**
	 * Visit an abstract syntax tree with a visitor.
	 * 
	 * @param tree
	 *            the abstract syntax tree to visit.
	 * @param visitor
	 *            the visitor to visit the tree.
	 * @throws T
	 *             if an error occurs in the visitor.
	 * @throws RecognitionException
	 */
	private static <T extends RecognitionException, U, V, W, X, Y, Z> void visit(CommonTree tree,
			TreeWalker<T, U, V, W, X, Y, Z> visitor) throws T, RecognitionException {
		TreeNodeStream nodes = new CommonTreeNodeStream(tree);
		TarrhTreeWalker treeWalker = new TarrhTreeWalker(visitor, nodes);
		treeWalker.program();
	}
}
